<!DOCTYPE html>
<html lang="zh-Hans">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 5.3.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/blog/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/blog/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/blog/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/blog/images/logo.svg" color="#222">

<link rel="stylesheet" href="/blog/css/main.css">

<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Lato:300,300italic,400,400italic,700,700italic&display=swap&subset=latin,latin-ext">
<link rel="stylesheet" href="/blog/lib/font-awesome/css/font-awesome.min.css">


<script id="hexo-configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    hostname: new URL('https://guodh.gitee.io/blog').hostname,
    root: '/blog/',
    scheme: 'Pisces',
    version: '7.7.1',
    exturl: false,
    sidebar: {"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},
    copycode: {"enable":true,"show_result":true,"style":null},
    back2top: {"enable":true,"sidebar":false,"scrollpercent":false},
    bookmark: {"enable":false,"color":"#222","save":"auto"},
    fancybox: false,
    mediumzoom: false,
    lazyload: false,
    pangu: false,
    comments: {"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},
    algolia: {
      appID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    },
    localsearch: {"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},
    path: 'search.xml',
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}
  };
</script>

  <meta name="description" content="写下我的点点滴滴，写下我对这个世界的看法，还有我内心锁崇拜的技术">
<meta property="og:type" content="website">
<meta property="og:title" content="蛋蛋的小屋">
<meta property="og:url" content="https://guodh.gitee.io/blog/page/7/index.html">
<meta property="og:site_name" content="蛋蛋的小屋">
<meta property="og:description" content="写下我的点点滴滴，写下我对这个世界的看法，还有我内心锁崇拜的技术">
<meta property="og:locale">
<meta property="article:author" content="RunningEgg">
<meta name="twitter:card" content="summary">

<link rel="canonical" href="https://guodh.gitee.io/blog/page/7/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome: true,
    isPost: false
  };
</script>

  <title>蛋蛋的小屋</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-meta">

    <div>
      <a href="/blog/" class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">蛋蛋的小屋</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
        <p class="site-subtitle">来了就看看吧，没有刀剑只有故事</p>
  </div>

  <div class="site-nav-toggle">
    <div class="toggle" aria-label="Toggle navigation bar">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>
</div>


<nav class="site-nav">
  
  <ul id="menu" class="menu">
        <li class="menu-item menu-item-home">

    <a href="/blog/" rel="section"><i class="fa fa-fw fa-home"></i>Home</a>

  </li>
        <li class="menu-item menu-item-about">

    <a href="/blog/about/" rel="section"><i class="fa fa-fw fa-user"></i>About</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/blog/archives/" rel="section"><i class="fa fa-fw fa-archive"></i>Archives</a>

  </li>
        <li class="menu-item menu-item-book">

    <a href="/blog/book/" rel="section"><i class="fa fa-fw fa-archive"></i>book</a>

  </li>
  </ul>

</nav>
</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content">
            

  <div class="posts-expand">
        
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block home" lang="zh-Hans">
    <link itemprop="mainEntityOfPage" href="https://guodh.gitee.io/blog/2018/02/25/2018/rocketmq%E9%97%AE%E9%A2%98/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/blog/images/avatar.gif">
      <meta itemprop="name" content="RunningEgg">
      <meta itemprop="description" content="写下我的点点滴滴，写下我对这个世界的看法，还有我内心锁崇拜的技术">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="蛋蛋的小屋">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          
            <a href="/blog/2018/02/25/2018/rocketmq%E9%97%AE%E9%A2%98/" class="post-title-link" itemprop="url">RocketMq中MessageQueue的分配</a>
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              <span class="post-meta-item-text">Posted on</span>
              

              <time title="Created: 2018-02-25 21:21:21 / Modified: 09:31:27" itemprop="dateCreated datePublished" datetime="2018-02-25T21:21:21+08:00">2018-02-25</time>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <p>都知道Rocketmq中有ConsumerGroup的概念。在集群模式下，多台服务器配置相同的ConsumerGroup，能够使得每次只有一台服务器消费消息（注意，但不保证只消费一次，存在网络抖动的情况）。那么，笔者就很疑惑，Rocketmq是如何实现这个模式的？如何保证只有一台服务器消费？</p>
<p>虽然答案很简单，但却是一个很好的带着问题看源码的机会。</p>
<h2 id="RocketMq结构"><a href="#RocketMq结构" class="headerlink" title="RocketMq结构"></a>RocketMq结构</h2><img src="https://olwr1lamu.qnssl.com/RocketMq%E7%A4%BA%E6%84%8F%E5%9B%BE.png" alt="RocketMQ架构示意图"/>

<p>从图中可以看到，MQ主要投递消息和拉取消息两个环节。</p>
<p>众多的架构都是顺应时代潮流而来，Rocketmq的结构体系当然也不是阿里所独创的，而是依据AMQP协议而来。Rocketmq中的Producer，Broker，以及Consumer都是依据AMQP中的概念衍生出来的。所以这里不妨讲讲AMQP（Advanced Message Queuing Protocal，高级消息队列协议），便于大家更好的理解技术的发展过程。</p>
<blockquote>
<p>paper下载 <a href="http://www.amqp.org/specification/0-9-1/amqp-org-download">http://www.amqp.org/specification/0-9-1/amqp-org-download</a> </p>
</blockquote>
<ul>
<li>Broker: 接收和分发的应用</li>
<li>Virtual host:出于多租户和安全因素，把AMQP的基本组件划分到一个虚拟分组中。各个租户之间是网络隔离的，类似Linux中的namespace概念（可自行Google）</li>
<li>Connection：publisher/consumer 和broker之间的TCP连接</li>
<li>Channel：是相较于Connection更加轻量的连接，是Connection上的逻辑连接</li>
<li>Exchange: 负责将message分发到不同的Queue中</li>
<li>Queue: 消息最终会落到Queue中，消息由Broker push给Consumer或者由Consumer来pull消息</li>
<li>Binding：exchange和queue之间的消息路由策略</li>
</ul>
<img src="https://olwr1lamu.qnssl.com/AMQP.png" alt="AMQP架构示意图"/>

<h2 id="消息队列的3大类型"><a href="#消息队列的3大类型" class="headerlink" title="消息队列的3大类型"></a>消息队列的3大类型</h2><p>当然基于这样一个协议，不单单是RocketMq一个闪耀在消息队列选型中，还有不同的消息队列。</p>
<blockquote>
<p><a href="https://mp.weixin.qq.com/s/B1D-J_1wpaqj0sxcmaArbQ">https://mp.weixin.qq.com/s/B1D-J_1wpaqj0sxcmaArbQ</a></p>
</blockquote>
<p>主要分为了3大阵营:</p>
<ul>
<li>有Broker 重Topic流：kafka，JMS</li>
<li>有Broker 轻Topic流: RocketMQ</li>
<li>无Broker: ZeroMQ</li>
</ul>
<p>当然，如果熟悉了AMQP协议，你也可以选择自研一个消息队列</p>
<blockquote>
<p><a href="https://zhuanlan.zhihu.com/p/28967866">https://zhuanlan.zhihu.com/p/28967866</a></p>
</blockquote>
<p>了解了一些背景，来看下RocketMQ中消息的投递过程。还是那个具体的问题，RocketMQ是如何选择一个队列来投递的呢？</p>
<h2 id="Producer如何投递消息到不同队列"><a href="#Producer如何投递消息到不同队列" class="headerlink" title="Producer如何投递消息到不同队列"></a>Producer如何投递消息到不同队列</h2><p>这里提一下，RocketMq中所有关于生产者和消费者的代码都在client包下。打开源码，可以看到Procuder下有个selector包，看到这个包是不是感到就是它的感觉。</p>
<p>可以看到selector下的三个类都是实现了MessageQueueSelector，来看下MessageQueueSelector的代码。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">MessageQueueSelector</span> </span>&#123;</span><br><span class="line">    <span class="function">MessageQueue <span class="title">select</span><span class="params">(<span class="keyword">final</span> List&lt;MessageQueue&gt; mqs, <span class="keyword">final</span> Message msg, <span class="keyword">final</span> Object arg)</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MessageQueue</span> </span>&#123;</span><br><span class="line">	<span class="keyword">private</span> String topic;</span><br><span class="line">	<span class="keyword">private</span> String brokerName;</span><br><span class="line">	<span class="keyword">private</span> <span class="keyword">int</span> queueId;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>看一下哪里调用了MessageQueueSelector.select()，发现是DefaultMQProducerImpl，那么可以确认就是由MessageQueueSelector提供了选择哪个队列。</p>
<p>RocketMq提供了3种不同的选择队列方式:</p>
<ul>
<li>SelectMessageQueueByHash</li>
<li>SelectMessageQueueByMachineRoom</li>
<li>SelectMessageQueueByRandom</li>
</ul>
<h3 id="默认队列数量"><a href="#默认队列数量" class="headerlink" title="默认队列数量"></a>默认队列数量</h3><p>细心的同学肯定会问那么队列数量是无限大的吗？这个可以查阅RocketMq的使用手册，默认的队列数量是4 (defaultTopicQueueNums: 4)，当然你也可以选择自己配置。</p>
<p>同时不知道有没有同学找错地儿，笔者刚开始是找错地儿了，在TopicPublishInfo中也找到了个selectOneMessageQueue，代码如下。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TopicPublishInfo</span></span>&#123;</span><br><span class="line">    <span class="comment">// 不同版本，代码有些不同，逻辑类似</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> MessageQueue <span class="title">selectOneMessageQueue</span><span class="params">(<span class="keyword">final</span> String lastBrokerName)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (lastBrokerName != <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">int</span> index = <span class="keyword">this</span>.sendWhichQueue.getAndIncrement();</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="keyword">this</span>.messageQueueList.size(); i++) &#123;</span><br><span class="line">                <span class="keyword">int</span> pos = Math.abs(index++) % <span class="keyword">this</span>.messageQueueList.size();</span><br><span class="line">                MessageQueue mq = <span class="keyword">this</span>.messageQueueList.get(pos);</span><br><span class="line">                <span class="keyword">if</span> (!mq.getBrokerName().equals(lastBrokerName)) &#123;</span><br><span class="line">                    <span class="keyword">return</span> mq;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">int</span> index = <span class="keyword">this</span>.sendWhichQueue.getAndIncrement();</span><br><span class="line">            <span class="keyword">int</span> pos = Math.abs(index) % <span class="keyword">this</span>.messageQueueList.size();</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">this</span>.messageQueueList.get(pos);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>查了下调用方发现是MQFaultStrategy，看来是Rocketmq消费失败时候，会将消息重新投递到不同的队列，这样在集群模式下能够保证分布到不同机器消费。（是不是还有疑惑，为什么能保证到不同机器，请往下看）</p>
<h2 id="Consumer如何从消息队列获取消息"><a href="#Consumer如何从消息队列获取消息" class="headerlink" title="Consumer如何从消息队列获取消息"></a>Consumer如何从消息队列获取消息</h2><p>这里是比较难理解的一步，首先查阅RocketMQ手册可以看到：</p>
<blockquote>
<p>RocketMQ 的 Consumer 都是从 Broker 拉消息来消费，但是为了能做到实时收消息，RocketMQ 使用长轮询方式，可以保证消息实时性同 Push 方式一致。返种长轮询方式类似亍 Web QQ 收収消息机制。请参考以下信息了解更多。<a href="http://www.ibm.com/developerworks/cn/web/wa-lo-comet/">http://www.ibm.com/developerworks/cn/web/wa-lo-comet/</a></p>
</blockquote>
<p>虽然解释的很详细，但是对新手还是不是很友好。简单的来说，就是使用长轮询，客户端发起请求和服务端先连接上，但是如果服务端没有数据，这是连接还是hold住，当有数据push给客户端的时候才关闭连接。这样不但保证了消费者不会被上游的消息打垮，也保证了消息的实时性。</p>
<p>那么还有个问题，Consumer如何从MessageQueue上拉取消息呢？是随机拉吗？</p>
<p>不妨来看下MQPullConsumer，DefaultMQPullConsumer就是继承于它。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MQPullConsumer</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 拉消息,非阻塞</span></span><br><span class="line">    <span class="comment">// </span></span><br><span class="line">    <span class="comment">// @param mq from which message queue</span></span><br><span class="line">    <span class="comment">// @param subExpression 订阅的tag，只支持&quot;tag1 || tag2 || tag3&quot;</span></span><br><span class="line">    <span class="comment">// @param offset 标志位</span></span><br><span class="line">    <span class="comment">// @param maxNums 消费最大数量</span></span><br><span class="line">    <span class="function">PullResult <span class="title">pull</span><span class="params">(<span class="keyword">final</span> MessageQueue mq, <span class="keyword">final</span> String subExpression, <span class="keyword">final</span> <span class="keyword">long</span> offset,</span></span></span><br><span class="line"><span class="function"><span class="params">	    <span class="keyword">final</span> <span class="keyword">int</span> maxNums)</span> <span class="keyword">throws</span> MQClientException, RemotingException, MQBrokerException,</span></span><br><span class="line"><span class="function">	    InterruptedException</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>可以看到MessageQueue是传进来的，这就比较尴尬了，实在无法理解是什么时候决定好从哪个队列拉取消息的。幸亏有万能的搜索引擎，</p>
<blockquote>
<p><a href="https://zhuanlan.zhihu.com/p/25140744">https://zhuanlan.zhihu.com/p/25140744</a></p>
</blockquote>
<p>RocketMq有专门的类AllocateMessageQueueStrategy.class，就藏在Client.Consumer.rebalance包下。</p>
<ul>
<li>AllocateMessageQueueAveragely</li>
<li>AllocateMessageQueueAveragelyByCircle</li>
<li>AllocateMessageQueueByConfig</li>
<li>AllocateMessageQueueByMachineRoom</li>
<li>AllocateMessageQueueConsistentHash</li>
</ul>
<p>每一次Consumer数量的变更，都会触发AllocateMessageQueueStrategy。也就是每一次Consumer拉取的队列都是固定好的。</p>
<p>现在，在回过头来看看第一张RocketMQ的架构图，是不是觉得画的很透彻。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ol>
<li>任何的框架都有它衍生变化的历史，了解架构变化的历史，才能更好的理解一个框架</li>
<li>好好研读使用手册，包含了很多架构的细节</li>
<li>带着问题去研读源码</li>
</ol>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

        
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block home" lang="zh-Hans">
    <link itemprop="mainEntityOfPage" href="https://guodh.gitee.io/blog/2018/01/28/2018/AQS%E6%BA%90%E7%A0%81%E7%90%86%E8%A7%A3/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/blog/images/avatar.gif">
      <meta itemprop="name" content="RunningEgg">
      <meta itemprop="description" content="写下我的点点滴滴，写下我对这个世界的看法，还有我内心锁崇拜的技术">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="蛋蛋的小屋">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          
            <a href="/blog/2018/01/28/2018/AQS%E6%BA%90%E7%A0%81%E7%90%86%E8%A7%A3/" class="post-title-link" itemprop="url">AQS源码理解</a>
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              <span class="post-meta-item-text">Posted on</span>

              <time title="Created: 2018-01-28 00:00:01" itemprop="dateCreated datePublished" datetime="2018-01-28T00:00:01+08:00">2018-01-28</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="fa fa-calendar-check-o"></i>
                </span>
                <span class="post-meta-item-text">Edited on</span>
                <time title="Modified: 2019-02-05 21:56:40" itemprop="dateModified" datetime="2019-02-05T21:56:40+08:00">2019-02-05</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <p>很多人对于Java线程的并发都是停留在Lock层面，通过Lock能够将synchronized粗大的颗粒划分为很小的颗粒度。然而很多人可能并没有真正去了解Lock的实现原理。</p>
<p>而不妨一说，AQS就是依靠数据结构的FIFO queue和compareAndSet来现实了强大的并发控制。AQS本身不提供任何关于“锁”的功能，它只帮助你如何“排队”。</p>
          <!--noindex-->
            <div class="post-button">
              <a class="btn" href="/blog/2018/01/28/2018/AQS%E6%BA%90%E7%A0%81%E7%90%86%E8%A7%A3/#more" rel="contents">
                Read more &raquo;
              </a>
            </div>
          <!--/noindex-->
        
      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

        
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block home" lang="zh-Hans">
    <link itemprop="mainEntityOfPage" href="https://guodh.gitee.io/blog/2018/01/07/2018/LSM-Tree%E4%BB%8B%E7%BB%8D/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/blog/images/avatar.gif">
      <meta itemprop="name" content="RunningEgg">
      <meta itemprop="description" content="写下我的点点滴滴，写下我对这个世界的看法，还有我内心锁崇拜的技术">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="蛋蛋的小屋">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          
            <a href="/blog/2018/01/07/2018/LSM-Tree%E4%BB%8B%E7%BB%8D/" class="post-title-link" itemprop="url">LSM树的不同实现介绍</a>
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              <span class="post-meta-item-text">Posted on</span>

              <time title="Created: 2018-01-07 23:23:38" itemprop="dateCreated datePublished" datetime="2018-01-07T23:23:38+08:00">2018-01-07</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="fa fa-calendar-check-o"></i>
                </span>
                <span class="post-meta-item-text">Edited on</span>
                <time title="Modified: 2018-01-08 21:36:52" itemprop="dateModified" datetime="2018-01-08T21:36:52+08:00">2018-01-08</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <p>这个需要和BTree一起来讲，我们都知道BTree是Balance Tree，为了维持Balancing的特性，每次读入的时候需要对树进行调整，是一笔不小的时间损耗。在一些使用场景下（例如google爬取网页），需要高性能写入，而对读的要求并不是那么高。<br>因此，LSM树是基于这样的背景下发展出来的，针对类似的场景LSM提出了很多优化措施。</p>
<h2 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h2><p>举几个比较常见的例子：HBase、LevelDB、RocksDB</p>
<blockquote>
<p>HBase大家可能比较熟，LevelDB和RocksDB分别是Google和FaceBook推出的，同样也是开源的。对于英语好的同学可以直接看看官方的文档，对于LSM的工业实现会有一个更深入的理解。</p>
<p>为什么要看不同的DB的实现呢？因为虽然是同个数据结构，但是在实现上通过类似布隆过滤器等手段，不同的实现方式能够大大提升其效率，因此了解不同公司的LSM树实现还是很有必要的。</p>
</blockquote>
<h2 id="LSM树基础"><a href="#LSM树基础" class="headerlink" title="LSM树基础"></a>LSM树基础</h2><p>LSM Tree，全写Log-Structured Merge Tree。本质上通过顺序写入log的方式，大大的提升写入的性能，同时NoSQL的结构属性帮助其天然适合Shard。</p>
<h3 id="基本操作"><a href="#基本操作" class="headerlink" title="基本操作"></a>基本操作</h3><p>1.将数据写入内存memtable（同时硬盘中复制一份，保证系统宕机数据不丢失）<br>2.数据到达一定容量，将数据flush到硬盘SStable，同时对硬盘中的数据进行merge</p>
<h3 id="复杂之处-Compaction"><a href="#复杂之处-Compaction" class="headerlink" title="复杂之处:Compaction"></a>复杂之处:Compaction</h3><p>SStable在硬盘中不断增多，会带来读的巨大压力。这时候就需要“数据结构”所独特的价值之处了，通过不同的Compaction(压缩)来构建不同的数据结构，能够有效的减低读的效率。<br>用空间来换取时间，这也是HBase、LevelDB、RocksDB三者之间的真正区别所在。</p>
<h2 id="HBase中的LSM实现"><a href="#HBase中的LSM实现" class="headerlink" title="HBase中的LSM实现"></a>HBase中的LSM实现</h2><p>先来看下HBase中的实现，如图所示。</p>
<img src="https://olwr1lamu.qnssl.com/lsm.png" alt="hbase_lsm"/>
网络上很多提到了Tree的概念，其实HBase的实现并没有依托于Tree。大家肯定有个疑问，“HBase那不是会很慢？”事实上肯定不慢，正是靠着布隆过滤器，HBase很好的诠释了数据结构时间和空间之间的艺术。

<h3 id="布隆过滤器"><a href="#布隆过滤器" class="headerlink" title="布隆过滤器"></a>布隆过滤器</h3><p>布隆过滤器和动态缓存有点像，不过都是数据结构很典型的例子，使用空间大幅度的降低了时间的使用。<br><img src="https://olwr1lamu.qnssl.com/bloom.png" alt="bloom"/></p>
<p>bloom 借助的是一个大大大的位表以及多个hash函数，例如图中，将{x,y,z}的用3个hash函数映射到位表中。那么位表中有n个位置有标1,（3&lt;=n&lt;=9）。</p>
<ol>
<li>如果这个时候来了个w，w经过3个hash函数，对应到位表上的位置有一个映射的值为0，那么说明w肯定不在{x,y,x}中。</li>
<li>如果w的3个hash函数映射后，都为1，并不能够说明w在{x,y,z}中。有可能是类似上图4，5，6位置格，而那些格是有不同的值映射出来的。</li>
</ol>
<h3 id="HBase的Compaction"><a href="#HBase的Compaction" class="headerlink" title="HBase的Compaction"></a>HBase的Compaction</h3><p>如同上文所讲，Compaction才是一个LSM树的核心。HBase中分为两种：</p>
<ul>
<li>Minor Compaction</li>
<li>Major Compaction</li>
</ul>
<p>两者都是合并SStable(在HBase中SStable叫HFile)。Major Compaction在合并的时候回删除一些过期的Key，持续时间比较长，一般在业务低峰期手动触发。（业务低峰期可使用hbase.offpeak.start.hour和hbase.offpeak.end.hour配置）</p>
<p>同时，不同版本的HBase也提供了不同的Compaction策略让使用者来决策:</p>
<ul>
<li>RatioBasedCompactionPolicy</li>
<li>ExploringCompactionPolicy</li>
</ul>
<blockquote>
<p>两种具体介绍看:<a href="http://hbasefly.com/2016/07/13/hbase-compaction-1/">http://hbasefly.com/2016/07/13/hbase-compaction-1/</a></p>
</blockquote>
<p>因为笔者没有实际使用过HBase的经验，所以不敢大加妄论，各位看官可以通过上面的连接看看比较专业的介绍。但是，对于笔者这样的业务方而言，HBase没有提供索引，而是通过Scanner去查询数据，似乎这个数据结构不够“完美”。</p>
<h2 id="LevelDB-and-RocksDB"><a href="#LevelDB-and-RocksDB" class="headerlink" title="LevelDB and RocksDB"></a>LevelDB and RocksDB</h2><p>RocksDb是facebook团队基于google开发的LevelDB的升级版本，提供了很多LevelDB所不具备的feature。（延伸一下，TiDB是一个newSQL数据库，它的底层就是基于RocksDb。网上已经有很多公司已经将TiDB上到生产环境，笔者所在公司的数据团队也已经灰度的一部分数据到TiDB上了。说明RocksDb也是挺可靠的。）<br>LevelDB的文档比较少，所以一起来看RocksDB的实现吧。</p>
<blockquote>
<p>LevelDB文档地址: <a href="https://github.com/google/leveldb/blob/master/doc/impl.md">https://github.com/google/leveldb/blob/master/doc/impl.md</a><br>RocksDB文档地址: <a href="https://github.com/facebook/rocksdb/wiki">https://github.com/facebook/rocksdb/wiki</a></p>
</blockquote>
<h3 id="Compactions"><a href="#Compactions" class="headerlink" title="Compactions"></a>Compactions</h3><p>RocksDB也提供了多种的策略:</p>
<ul>
<li>Leveled style compaction</li>
<li>Universal style compaction</li>
<li>FIFO Compaction </li>
</ul>
<p>这里主要介绍下Leveled style compaction，因为他是起源于LevelDB，方便大家和LevelDB一同了解。</p>
<h3 id="Leveled-style-compaction"><a href="#Leveled-style-compaction" class="headerlink" title="Leveled style compaction"></a>Leveled style compaction</h3><img src="https://olwr1lamu.qnssl.com/rocksdb1.jpg" alt="rocksdb1"/>

<p>如上图，可以看到Leveled style将SStable划分为不同的Level，除了Level 1中可能存在重复的key之外，Level 2之后都不会有重复的key。同时每个SStable中，key都是有序的，不要小看了有序的作用，看到查找你就会知道他的妙用。</p>
<img src="https://olwr1lamu.qnssl.com/rocksdb2.jpg" alt="rocksdb2"/>
另外，每一层的容量是递增的，这个也比较好理解，如上图。

<img src="https://olwr1lamu.qnssl.com/rocksdb3.jpg" alt="rocksdb3"/>

<p>如上图，compaction的过程其实也比较简单，当某一个level的数量大于容量的时候，会选一个SStable与level+1的数据进行merge。选取的level+1的SStable是存在和当前SStable存在相同key的。如果生成的SStable导致level+1的容量也超过限度，那么继续往下merge。</p>
<p>比较吊的是，除了level0和level1的merge，其他的merge都是可以多线程一起进行的，因为是没有重复key的，所以不用担心并发问题。</p>
<h3 id="查找"><a href="#查找" class="headerlink" title="查找"></a>查找</h3><p>就像上面说的有序性能够极大的提升key查找的效率，查找通过两步:</p>
<ol>
<li>二分查找所有文件的start和end，找到可能存在key的file</li>
<li>二分查找file中所有包含的key</li>
</ol>
<p>所有的查找都是通过二分查找来实现的。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文从LSM介绍入手，概览了一些LSM的工程实现。由于还没深入了解这几种DB的源码，仅仅只能从文档中找到一些有价值的内容给大家看，希望能够抛砖迎玉，让大家重新体会到数据结构的妙用。如有内容不对或者想交流一下，请随时联系我~</p>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

        
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block home" lang="zh-Hans">
    <link itemprop="mainEntityOfPage" href="https://guodh.gitee.io/blog/2017/12/17/2017/%E5%A4%A7%E6%95%B0%E6%8D%AE%E5%B7%A5%E5%85%B7%E6%A6%82%E8%A7%88/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/blog/images/avatar.gif">
      <meta itemprop="name" content="RunningEgg">
      <meta itemprop="description" content="写下我的点点滴滴，写下我对这个世界的看法，还有我内心锁崇拜的技术">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="蛋蛋的小屋">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          
            <a href="/blog/2017/12/17/2017/%E5%A4%A7%E6%95%B0%E6%8D%AE%E5%B7%A5%E5%85%B7%E6%A6%82%E8%A7%88/" class="post-title-link" itemprop="url">大数据工具概览</a>
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              <span class="post-meta-item-text">Posted on</span>

              <time title="Created: 2017-12-17 12:03:51" itemprop="dateCreated datePublished" datetime="2017-12-17T12:03:51+08:00">2017-12-17</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="fa fa-calendar-check-o"></i>
                </span>
                <span class="post-meta-item-text">Edited on</span>
                <time title="Modified: 2018-01-17 23:07:13" itemprop="dateModified" datetime="2018-01-17T23:07:13+08:00">2018-01-17</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <p>现在企业都是面对巨大的数据量，那么作为一个服务端工程师，势必要了解最新的大数据工具。Hadoop，Storm，Sprak……大数据提供的工具也是眼花缭乱。所以，本文从网络上搜集了些大数据相关知识，便于让大家更加清晰的理解每个工具的不同之处。</p>
<p>涉及点:Hadoop，Storm，Samza，Spark，Flink</p>
          <!--noindex-->
            <div class="post-button">
              <a class="btn" href="/blog/2017/12/17/2017/%E5%A4%A7%E6%95%B0%E6%8D%AE%E5%B7%A5%E5%85%B7%E6%A6%82%E8%A7%88/#more" rel="contents">
                Read more &raquo;
              </a>
            </div>
          <!--/noindex-->
        
      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

        
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block home" lang="zh-Hans">
    <link itemprop="mainEntityOfPage" href="https://guodh.gitee.io/blog/2017/12/03/2017/Spring%E7%9A%84IOC%E3%80%81AOP%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/blog/images/avatar.gif">
      <meta itemprop="name" content="RunningEgg">
      <meta itemprop="description" content="写下我的点点滴滴，写下我对这个世界的看法，还有我内心锁崇拜的技术">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="蛋蛋的小屋">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          
            <a href="/blog/2017/12/03/2017/Spring%E7%9A%84IOC%E3%80%81AOP%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/" class="post-title-link" itemprop="url">Spring的IOC、AOP源码解析</a>
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              <span class="post-meta-item-text">Posted on</span>

              <time title="Created: 2017-12-03 13:48:10" itemprop="dateCreated datePublished" datetime="2017-12-03T13:48:10+08:00">2017-12-03</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="fa fa-calendar-check-o"></i>
                </span>
                <span class="post-meta-item-text">Edited on</span>
                <time title="Modified: 2017-12-17 12:21:09" itemprop="dateModified" datetime="2017-12-17T12:21:09+08:00">2017-12-17</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <p>Spring又是一个老生常谈的东西了，但是每一次看总能够发掘一些有意思的新东西。趁着最近在弥补一些基础的东西，赶紧把Spring源码在看看。如果你是一只刚入行1，2年的小菜鸟，也建议你也看看，看看Spring里面，是否有些“看似懂其实根本不懂他如何实现“的地方。</p>
<p>涉及点：Spring、源码解析</p>
          <!--noindex-->
            <div class="post-button">
              <a class="btn" href="/blog/2017/12/03/2017/Spring%E7%9A%84IOC%E3%80%81AOP%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/#more" rel="contents">
                Read more &raquo;
              </a>
            </div>
          <!--/noindex-->
        
      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

        
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block home" lang="zh-Hans">
    <link itemprop="mainEntityOfPage" href="https://guodh.gitee.io/blog/2017/11/26/2018/%E7%BA%BF%E7%A8%8B%E7%9F%A5%E8%AF%86%E6%A2%B3%E7%90%86/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/blog/images/avatar.gif">
      <meta itemprop="name" content="RunningEgg">
      <meta itemprop="description" content="写下我的点点滴滴，写下我对这个世界的看法，还有我内心锁崇拜的技术">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="蛋蛋的小屋">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          
            <a href="/blog/2017/11/26/2018/%E7%BA%BF%E7%A8%8B%E7%9F%A5%E8%AF%86%E6%A2%B3%E7%90%86/" class="post-title-link" itemprop="url">线程知识梳理</a>
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              <span class="post-meta-item-text">Posted on</span>

              <time title="Created: 2017-11-26 12:36:34" itemprop="dateCreated datePublished" datetime="2017-11-26T12:36:34+08:00">2017-11-26</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="fa fa-calendar-check-o"></i>
                </span>
                <span class="post-meta-item-text">Edited on</span>
                <time title="Modified: 2018-05-02 23:09:14" itemprop="dateModified" datetime="2018-05-02T23:09:14+08:00">2018-05-02</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <p>整理一些线程的知识，因为笔记喜欢广度优先的看问题，所以涉及的面比较大，但是推荐大家看看，说不定就有遗漏的知识点呢。<br>涉及点join、@sum.misc.Contended、CyclicBarrier、CountDownLatch、Semaphore、Exchanger。</p>
          <!--noindex-->
            <div class="post-button">
              <a class="btn" href="/blog/2017/11/26/2018/%E7%BA%BF%E7%A8%8B%E7%9F%A5%E8%AF%86%E6%A2%B3%E7%90%86/#more" rel="contents">
                Read more &raquo;
              </a>
            </div>
          <!--/noindex-->
        
      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

        
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block home" lang="zh-Hans">
    <link itemprop="mainEntityOfPage" href="https://guodh.gitee.io/blog/2017/10/12/2017/Spring%E4%BA%8B%E5%8A%A1%E4%BB%A5%E5%8F%8A%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/blog/images/avatar.gif">
      <meta itemprop="name" content="RunningEgg">
      <meta itemprop="description" content="写下我的点点滴滴，写下我对这个世界的看法，还有我内心锁崇拜的技术">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="蛋蛋的小屋">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          
            <a href="/blog/2017/10/12/2017/Spring%E4%BA%8B%E5%8A%A1%E4%BB%A5%E5%8F%8A%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1/" class="post-title-link" itemprop="url">Spring事务以及分布式事务</a>
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              <span class="post-meta-item-text">Posted on</span>

              <time title="Created: 2017-10-12 21:23:07" itemprop="dateCreated datePublished" datetime="2017-10-12T21:23:07+08:00">2017-10-12</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="fa fa-calendar-check-o"></i>
                </span>
                <span class="post-meta-item-text">Edited on</span>
                <time title="Modified: 2017-11-05 14:45:39" itemprop="dateModified" datetime="2017-11-05T14:45:39+08:00">2017-11-05</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <p>我们都知道在写代码的时候，只要使用@Transcation，那么Spring就能帮我们完成事务的处理。那么问题来了，</p>
<ol>
<li>它是如何来帮我们解决的呢？</li>
<li>还有如今都是分布式系统，如何来实现一个分布式事务呢？</li>
</ol>
<p>先把结论说出来，分布式事务其实和Spring的事务实现方式有点类似，你只要看了Spring事务的源码对分布式事务的理解也能有个不小的帮助。</p>
<h2 id="从Spring的事务讲起"><a href="#从Spring的事务讲起" class="headerlink" title="从Spring的事务讲起"></a>从Spring的事务讲起</h2><p>Spring事务的本质就是切面帮你处理Exception，如果有异常就帮你处理。Spring利用AOP的特性，解耦了Service和DB，这种思路在未来写代码过程中可以多多使用。<br><img src="https://olwr1lamu.qnssl.com/proxy.jpeg" width="40%" height="40%" alt="AOP 概念"/></p>
<h3 id="Spring代理实现"><a href="#Spring代理实现" class="headerlink" title="Spring代理实现"></a>Spring代理实现</h3><p>既然是通过切面实现，那么先来看下TransactionInterceptor的UML结构：<br><img src="https://olwr1lamu.qnssl.com/TransactionIntecptor.png" width="70%" height="70%" alt="TransactionInterceptor.java"/></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// TransactionInterceptor.java</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">invoke</span><span class="params">(<span class="keyword">final</span> MethodInvocation invocation)</span> <span class="keyword">throws</span> Throwable </span>&#123;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从BeanMap中找到需要代理的Bean</span></span><br><span class="line">Class&lt;?&gt; targetClass = (invocation.getThis() != <span class="keyword">null</span> ? AopUtils.getTargetClass(invocation.getThis()) : <span class="keyword">null</span>);</span><br><span class="line"><span class="comment">// 这个才是代理的大头</span></span><br><span class="line"><span class="keyword">return</span> invokeWithinTransaction(invocation.getMethod(), targetClass, </span><br><span class="line">	<span class="keyword">new</span> InvocationCallback() &#123;</span><br><span class="line">		<span class="meta">@Override</span></span><br><span class="line">		<span class="function"><span class="keyword">public</span> Object <span class="title">proceedWithInvocation</span><span class="params">()</span> <span class="keyword">throws</span> Throwable </span>&#123;</span><br><span class="line">			<span class="keyword">return</span> invocation.proceed();</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>还有个很有意思的点就是，写了一个简单的Callback（看下面），其实是为了调用invacation的proceed()，有意思。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="class"><span class="keyword">interface</span> <span class="title">InvocationCallback</span> </span>&#123;</span><br><span class="line">	<span class="function">Object <span class="title">proceedWithInvocation</span><span class="params">()</span> <span class="keyword">throws</span> Throwable</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>然后是实际代理的地方，让我们分两步来看：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> Object <span class="title">invokeWithinTransaction</span><span class="params">(Method method, Class&lt;?&gt; targetClass, <span class="keyword">final</span> InvocationCallback invocation)</span></span></span><br><span class="line"><span class="function">		<span class="keyword">throws</span> Throwable </span>&#123;</span><br><span class="line">	</span><br><span class="line">	<span class="comment">// 获得事务配置的一些参数	</span></span><br><span class="line">  <span class="keyword">final</span> TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);</span><br><span class="line">	</span><br><span class="line">  <span class="keyword">final</span> PlatformTransactionManager tm = determineTransactionManager(txAttr);</span><br><span class="line">  <span class="keyword">final</span> String joinpointIdentification = methodIdentification(method, targetClass);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (txAttr == <span class="keyword">null</span> || !(tm <span class="keyword">instanceof</span> CallbackPreferringPlatformTransactionManager)) &#123;</span><br><span class="line"></span><br><span class="line">  **TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);**</span><br><span class="line">		</span><br><span class="line">		<span class="comment">// 部分一：如果传入的是定义好的TransactionManager</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">else</span> &#123;</span><br><span class="line">  **TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);**</span><br><span class="line">	</span><br><span class="line">		<span class="comment">// 部分二：如果传入的是实现自CallbackPreferringPlatformTransactionManager</span></span><br><span class="line">  &#125;				</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>在if/else中处理的代码其实类似，只是高版本的Spring提供了回调函数，提供给使用者来处理不同情况下的触发情况。<br>需要关注的是，可以看到两部分中都有一个生成TransactionInfo的操作，而在createTransactionIfNecessary()中，会将事务的所有信息绑定到transactionInfoHolder即线程上。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">bindToThread</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">this</span>.oldTransactionInfo = transactionInfoHolder.get();</span><br><span class="line">  transactionInfoHolder.set(<span class="keyword">this</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>（部分一）然后来看下代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">Object retVal = <span class="keyword">null</span>;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">	<span class="comment">// 调用链路</span></span><br><span class="line">	retVal = invocation.proceedWithInvocation();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">catch</span> (Throwable ex) &#123;</span><br><span class="line">	<span class="comment">// 这里正是处理回滚，并处理txInfo，抛出错误</span></span><br><span class="line">	completeTransactionAfterThrowing(txInfo, ex);</span><br><span class="line">	<span class="keyword">throw</span> ex;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">finally</span> &#123;</span><br><span class="line">	cleanupTransactionInfo(txInfo);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 事务Commit</span></span><br><span class="line">commitTransactionAfterReturning(txInfo);</span><br><span class="line"><span class="keyword">return</span> retVal;</span><br></pre></td></tr></table></figure>
<p>（部分二）详细代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 如果实现了CallbackPreferringPlatformTransactionManager，那么调用者可以自己来组织遇到不同的Exception所做的操作。</span></span><br><span class="line"><span class="comment">// TransactionCallback会出现三种情况：</span></span><br><span class="line"><span class="comment">// 1. RuntimeException 2.ThrowableHolderException 3.ThrowableHolder</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过实现CallbackPreferringPlatformTransactionManager的execute来对上面的三种情况来处理</span></span><br><span class="line"><span class="comment">// 千万记住面向接口编程</span></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,</span><br><span class="line">	<span class="keyword">new</span> TransactionCallback&lt;Object&gt;() &#123;</span><br><span class="line">		<span class="comment">// 定义好了三种不同情况的返回情况</span></span><br><span class="line">	  <span class="function"><span class="keyword">public</span> Object <span class="title">doInTransaction</span><span class="params">(TransactionStatus status)</span> </span>&#123;</span><br><span class="line">		  TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);</span><br><span class="line">		    </span><br><span class="line">		    <span class="keyword">try</span> &#123;</span><br><span class="line">			  <span class="keyword">return</span> invocation.proceedWithInvocation();</span><br><span class="line">			&#125; <span class="keyword">catch</span> (Throwable ex) &#123;</span><br><span class="line">			  <span class="keyword">if</span> (txAttr.rollbackOn(ex)) &#123;</span><br><span class="line">			    <span class="comment">// 情况1，2：需要处理的，可能都是回滚的情况</span></span><br><span class="line">			    <span class="keyword">if</span> (ex <span class="keyword">instanceof</span> RuntimeException) &#123;</span><br><span class="line">			      <span class="keyword">throw</span> (RuntimeException) ex;</span><br><span class="line">			    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">			    	<span class="keyword">throw</span> <span class="keyword">new</span> ThrowableHolderException(ex);</span><br><span class="line">			    &#125;</span><br><span class="line">			  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">				<span class="comment">// 情况3：代表正常情况</span></span><br><span class="line">				<span class="keyword">return</span> <span class="keyword">new</span> ThrowableHolder(ex);</span><br><span class="line">			  &#125;</span><br><span class="line">			&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">			  cleanupTransactionInfo(txInfo);</span><br><span class="line">			&#125;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 有可能需要重新抛出，spring为使用者做好了准备</span></span><br><span class="line">  <span class="keyword">if</span> (result <span class="keyword">instanceof</span> ThrowableHolder) &#123;</span><br><span class="line">    <span class="keyword">throw</span> ((ThrowableHolder) result).getThrowable();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">else</span> &#123;</span><br><span class="line">	<span class="keyword">return</span> result;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">catch</span> (ThrowableHolderException ex) &#123;</span><br><span class="line">	<span class="keyword">throw</span> ex.getCause();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="Spring真正回滚实现"><a href="#Spring真正回滚实现" class="headerlink" title="Spring真正回滚实现"></a>Spring真正回滚实现</h3><p>看了这么多，这下终于要到真正实现回滚的地方啦。也就是部分一中，catch的代码实现。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">processRollback</span><span class="params">(DefaultTransactionStatus status)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">try</span> &#123;</span><br><span class="line">		triggerBeforeCompletion(status);</span><br><span class="line">		<span class="keyword">if</span> (status.hasSavepoint()) &#123;</span><br><span class="line">			status.rollbackToHeldSavepoint();</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">else</span> <span class="keyword">if</span> (status.isNewTransaction()) &#123;</span><br><span class="line">			doRollback(status);</span><br><span class="line">		&#125;</span><br><span class="line">	<span class="comment">// …………省略一堆代码</span></span><br><span class="line">	<span class="keyword">catch</span>()&#123;&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>最主要的就是上面两种情况，<br>情况一：是有savePoint的<br>情况二：是没有savePoint的<br>需要说明的是，SAVEPOINT：数据库中在事务内部创建一系列可以 ROLLBACK 的还原点。<br>说道savePoint就必须提到Spring支持的事务传播级别：</p>
<table>
<thead>
<tr>
<th>参数</th>
<th>概念</th>
</tr>
</thead>
<tbody><tr>
<td>PROPAGATION_REQUIRED</td>
<td>支持当前事务</td>
</tr>
<tr>
<td>PROPAGATION_NESTED</td>
<td>嵌套事务</td>
</tr>
<tr>
<td>……</td>
<td>……</td>
</tr>
</tbody></table>
<p>这两个的区别就是savePoint的区别，如果是PROPAGATION_REQUIRED，则被嵌套的事务失败，那么所有的事务都会失败。而如果是PROPAGATION_NESTED，会回滚到savePoint。</p>
<p>继续来看代码内容：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">rollbackToHeldSavepoint</span><span class="params">()</span> <span class="keyword">throws</span> TransactionException </span>&#123;</span><br><span class="line">	getSavepointManager().rollbackToSavepoint(getSavepoint());</span><br><span class="line">	getSavepointManager().releaseSavepoint(getSavepoint());</span><br><span class="line">	setSavepoint(<span class="keyword">null</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">rollbackToSavepoint</span><span class="params">(Object savepoint)</span> <span class="keyword">throws</span> TransactionException </span>&#123;</span><br><span class="line">	ConnectionHolder conHolder = getConnectionHolderForSavepoint();</span><br><span class="line">	<span class="keyword">try</span> &#123;</span><br><span class="line">		conHolder.getConnection().rollback((Savepoint) savepoint);</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">catch</span> (Throwable ex) &#123;...&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="TCC-Transcation-和-2PC-Transcation-与Spring-事务回滚的异曲同工之处"><a href="#TCC-Transcation-和-2PC-Transcation-与Spring-事务回滚的异曲同工之处" class="headerlink" title="TCC-Transcation 和 2PC-Transcation 与Spring 事务回滚的异曲同工之处"></a>TCC-Transcation 和 2PC-Transcation 与Spring 事务回滚的异曲同工之处</h2><p>针对分布式事务，笔者看了两种解决方案：</p>
<blockquote>
<p>TCC-Transcation: <a href="https://github.com/changmingxie/tcc-transaction">https://github.com/changmingxie/tcc-transaction</a><br>2PC-Transcation: <a href="https://github.com/yu199195/happylifeplat-transaction/">https://github.com/yu199195/happylifeplat-transaction/</a></p>
</blockquote>
<p>发现其实都是类似的方案，通过记录各个阶段的状态，来保证事务的完整性。分布式事务提出了一个TranscationManager的概念，所有的事务状态都是TranscationManager来进行持久化，即使发生了事务中断，恢复之后也能通过TranscationManager来回滚。</p>
<p>而如果是调用不同的Service直接返回Exception了，就比较好解决。通过catch Exception，并且通过记录原始值来恢复。</p>
<p>粗略的来看下，先是</p>
<h3 id="2PC-Transcation："><a href="#2PC-Transcation：" class="headerlink" title="2PC-Transcation："></a>2PC-Transcation：</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@TxTransaction</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">testStockFail</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    orderService.save(order)；</span><br><span class="line"> <span class="comment">// 假设这里失败了</span></span><br><span class="line">    stockService.fail(stock);</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;stock_fail&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@TxTransaction</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fail</span><span class="params">(Stock stock)</span> </span>&#123;</span><br><span class="line">    stockMapper.save(<span class="keyword">null</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这里的每个Service都有@TxTransaction注解。每一次调用，都将会在TranscationManager中注册，如果发生了失败，则通过db的Canal去处理所有的事务。</p>
<p>再来看下</p>
<h3 id="TCC-Transcation："><a href="#TCC-Transcation：" class="headerlink" title="TCC-Transcation："></a>TCC-Transcation：</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Compensable(confirmMethod = &quot;confirmRecord&quot;, cancelMethod = &quot;cancelRecord&quot;, transactionContextEditor = MethodTransactionContextEditor.class)</span></span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">record</span><span class="params">(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto)</span> </span>&#123;    </span><br><span class="line">……</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">confirmRecord</span><span class="params">(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto)</span> </span>&#123;    </span><br><span class="line">……    </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Transactional</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">cancelRecord</span><span class="params">(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto)</span> </span>&#123;</span><br><span class="line"> ……    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>TCC从代码上来比较清楚，通过切面来处理不同情况下调用什么方法。</p>
<p>其实是不是和Spring事务中的try、catch、finally很像？</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>见微知著，推荐大家可以从小的地方开始，从Spring的事务来分析分布式事务，收获是大大滴。(^o^)/~</p>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

        
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block home" lang="zh-Hans">
    <link itemprop="mainEntityOfPage" href="https://guodh.gitee.io/blog/2017/10/03/2017/Paxos%E7%AE%97%E6%B3%95/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/blog/images/avatar.gif">
      <meta itemprop="name" content="RunningEgg">
      <meta itemprop="description" content="写下我的点点滴滴，写下我对这个世界的看法，还有我内心锁崇拜的技术">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="蛋蛋的小屋">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          
            <a href="/blog/2017/10/03/2017/Paxos%E7%AE%97%E6%B3%95/" class="post-title-link" itemprop="url">Paxos算法</a>
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              <span class="post-meta-item-text">Posted on</span>

              <time title="Created: 2017-10-03 17:21:52" itemprop="dateCreated datePublished" datetime="2017-10-03T17:21:52+08:00">2017-10-03</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="fa fa-calendar-check-o"></i>
                </span>
                <span class="post-meta-item-text">Edited on</span>
                <time title="Modified: 2017-10-30 22:14:13" itemprop="dateModified" datetime="2017-10-30T22:14:13+08:00">2017-10-30</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <p>作为一个后端开发对于分布式的理解少的可怜，这也难怪国内和国外的程序员有差距了吧，感觉是应该好好学习一波了。</p>
<p>Paxos算法的目的是：保证所有的机器在即使有部分crash的情况下，也能得出一致的结果。所以说，如果你说Paxos是强一致算法的话其实是错误的，应该是让机器达成共识。(￣▽￣)~* </p>
<p>Paxos作为分布式最经典的算法，可以简单的分为两种（其实演化的有好多种，主要是这两种）Basic Paxos和Multi Paxos。</p>
<p>如果英文好的话：可以直接看视频<br>资料来源：<a href="https://ramcloud.stanford.edu/~ongaro/userstudy/paxos.pdf">https://ramcloud.stanford.edu/~ongaro/userstudy/paxos.pdf</a><br>视频资源：<a href="https://www.youtube.com/watch?v=JEpsBg0AO6o&amp;t=584s">https://www.youtube.com/watch?v=JEpsBg0AO6o&amp;t=584s</a></p>
<h2 id="Basic-Paxos"><a href="#Basic-Paxos" class="headerlink" title="Basic Paxos"></a>Basic Paxos</h2><h3 id="目标"><a href="#目标" class="headerlink" title="目标"></a>目标</h3><p>Basic Paxos所要达到的目标有两个：</p>
<h4 id="1-safety"><a href="#1-safety" class="headerlink" title="1. safety"></a>1. safety</h4><ul>
<li>只有一个“提议”被选择</li>
<li>被选择的“提议”一定来自所属机器</li>
</ul>
<h4 id="2-Liveness"><a href="#2-Liveness" class="headerlink" title="2. Liveness"></a>2. Liveness</h4><ul>
<li>“提议” 最终会达成一致</li>
<li>如果“提议”达成一致，所有的机器都能感知到（包括挂掉的机器后来恢复了）</li>
</ul>
<h3 id="成员"><a href="#成员" class="headerlink" title="成员"></a>成员</h3><p>Proposers（提议者）和 Acceptors （接受者）</p>
<h3 id="提议Proposal组成"><a href="#提议Proposal组成" class="headerlink" title="提议Proposal组成"></a>提议Proposal组成</h3><table>
<thead>
<tr>
<th align="left">组成</th>
<th align="right">回合数</th>
<th align="center">服务器ID</th>
</tr>
</thead>
<tbody><tr>
<td align="left">组成</td>
<td align="right">Round Numver</td>
<td align="center">Server Id</td>
</tr>
</tbody></table>
<p>每个机器保存最大的回合数，机器不接受回合数小于当前回合数的提议。<br>服务器ID保证提议来自集群，同时一个服务器只产生一个提议。</p>
<h3 id="两阶段到达"><a href="#两阶段到达" class="headerlink" title="两阶段到达"></a>两阶段到达</h3><h4 id="阶段一：通知准备"><a href="#阶段一：通知准备" class="headerlink" title="阶段一：通知准备"></a>阶段一：通知准备</h4><ul>
<li>找出所有的提议</li>
<li>锁住所有的提议，直到完成</li>
</ul>
<h4 id="阶段二：通知接受"><a href="#阶段二：通知接受" class="headerlink" title="阶段二：通知接受"></a>阶段二：通知接受</h4><ul>
<li>询问Acceptors接受提议</li>
</ul>
<h3 id="两阶段过程"><a href="#两阶段过程" class="headerlink" title="两阶段过程"></a>两阶段过程</h3><table>
<thead>
<tr>
<th align="left">Basic Paxos</th>
<th align="right">Proposers</th>
<th align="center">Acceptos</th>
</tr>
</thead>
<tbody><tr>
<td align="left">第一步</td>
<td align="right">生成一个提议号码n（递增）</td>
<td align="center"></td>
</tr>
<tr>
<td align="left">第二步</td>
<td align="right">通知提议Proposal(n)到所有机器</td>
<td align="center"></td>
</tr>
<tr>
<td align="left">第三步</td>
<td align="right"></td>
<td align="center">如果n大于当前的回合数，则回应，return AcceptedValue(n)</td>
</tr>
<tr>
<td align="left">第四步</td>
<td align="right">如果大多数服务器返回接受,则将acceptedProposal更新为acceptedValue</td>
<td align="center"></td>
</tr>
<tr>
<td align="left">第五步</td>
<td align="right">广播Accept(n, value)到所有机器</td>
<td align="center"></td>
</tr>
<tr>
<td align="left">第六步</td>
<td align="right"></td>
<td align="center">回应接受Accept(n, value)</td>
</tr>
<tr>
<td align="left">第七步</td>
<td align="right">如果result的中存在返回的result中回合数大于n的重新返回第一步，否则这个提议被选中</td>
<td align="center"></td>
</tr>
</tbody></table>
<p><strong>Acceptors must record minProposal, acceptedProposal,and acceptedValue on stable storage (disk)</strong></p>
<h3 id="缺点"><a href="#缺点" class="headerlink" title="缺点:"></a>缺点:</h3><p>难实现<br>效率低（两次RPC）<br>活锁（解决方案：Random TimeOut）</p>
<h2 id="Multi-Paxos"><a href="#Multi-Paxos" class="headerlink" title="Multi Paxos"></a>Multi Paxos</h2><p>Multi Paxos是基于Basic Paxos提出来的，通过选举出一个Leader，来减少RPC调用（只要Leader不挂，就不需要再次选举）。</p>
<p>具体的实现可以看下文的Raft，Raft只是Multi Paxos的简化实践版本。</p>
<h2 id="Raft-Consensus-Algorithm"><a href="#Raft-Consensus-Algorithm" class="headerlink" title="Raft Consensus Algorithm"></a>Raft Consensus Algorithm</h2><p>动画演示地址：<a href="http://thesecretlivesofdata.com/raft/">http://thesecretlivesofdata.com/raft/</a></p>
<h3 id="三个状态"><a href="#三个状态" class="headerlink" title="三个状态"></a>三个状态</h3><p>Follower、Candidate、Leader<br>其实就只有两个（Follower和Leader），当失去Leader，Follower会成为Candidate，尝试去成为Leader。</p>
<h3 id="两个Time-Out"><a href="#两个Time-Out" class="headerlink" title="两个Time Out"></a>两个Time Out</h3><p>上文说道，其实节点只有两种状态Follower和Leader，两个Time Out就是各自的时间钟。</p>
<ul>
<li><p>Election Time Out<br>Election Time Out 是Follower的时间钟，如果Follower在Election Time Out时间内没有收到Heart Beat，那么就认为Leader已经挂掉，那么此时这个节点会申请成为Leader。同时存在这种情况，两个节点同时发出了申请，解决方案就是各自随机一个时间（random time）进行选举，random time小的那个会先向所有的节点发出申请。</p>
</li>
<li><p>Heart Beat Time Out<br>节点成为Leader之后，需要每隔Heart Beat Time Out向节点发送心跳，告知自己是存活的。而且这个Heart Beat不仅仅是Heart Beat，有时还包括了写请求。</p>
</li>
</ul>
<h3 id="Leader-election"><a href="#Leader-election" class="headerlink" title="Leader election"></a>Leader election</h3><p>上面也已经讲了，每个节点都有一个自己的时间钟，在Time out这间没有收到请求，那么节点就认为Leader挂了，就会申请成为Leader。此时，如果收到了n/2+1的同意，就能成为Leader。所以节点数一般都是3，5，7，这样保证了，即使因为网络隔断，节点之间存在分区。比如5个节点，可能2，3分区，这样的情况下3个节点的分区仍能够选出Leader。</p>
<h3 id="Log-Replition"><a href="#Log-Replition" class="headerlink" title="Log Replition"></a>Log Replition</h3><p>日志提交也分为两步<br>第一步：Leader先自己将请求写入log，然后向所有节点发出写请求，每个节点收到请求（请求和Basic paxos一样，包含回合数），并将情切写到log中，同时节点如果成功将请求写入log，返回信息给Leader。</p>
<p>第二部：Leader接收到节点的返回值，如果数量大于n/2+1，那么认为这个是成功的，自己将log中请求commit，通知所有节点commit。</p>
<img src="https://olwr1lamu.qnssl.com/raft.png" width="70%" height="70%" alt="Log Replition过程"/>

<p>需要补充的是：log包含有log index和term number。Leader记住最大的log index。<br>当一个leader重新选举出来的时候（这是可能存在分区状况，例如上面说的情况），leader的log可能更fowller的log不一致，会强制follower与它一致。会先找出之间编号一致最大的log，从后面的覆盖掉。</p>
<h2 id="ZAB算法"><a href="#ZAB算法" class="headerlink" title="ZAB算法"></a>ZAB算法</h2><p>ZAB算法和Raft算法类似，zookeeper就是用的ZAB算法，不同的是ZAB是由follower向Leader发送心跳。类似一个push，一个是pull的感觉。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Raft是最好理解的版本，有兴趣的同学可以动手实现一下。这里是MIT大学的作业，使用Go实现Raft等算法的，博主也正在尝试中。<br>地址：<a href="http://www.jianshu.com/p/fcda437e560a">http://www.jianshu.com/p/fcda437e560a</a><br>网上的老司机的作业答案：<a href="https://github.com/happyer/distributed-computing">https://github.com/happyer/distributed-computing</a><br>记得给他们点个赞。</p>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

        
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block home" lang="zh-Hans">
    <link itemprop="mainEntityOfPage" href="https://guodh.gitee.io/blog/2017/09/30/2018/%E4%BA%8B%E4%BB%B6%E9%A9%B1%E5%8A%A8%E6%A8%A1%E5%9E%8B/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/blog/images/avatar.gif">
      <meta itemprop="name" content="RunningEgg">
      <meta itemprop="description" content="写下我的点点滴滴，写下我对这个世界的看法，还有我内心锁崇拜的技术">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="蛋蛋的小屋">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          
            <a href="/blog/2017/09/30/2018/%E4%BA%8B%E4%BB%B6%E9%A9%B1%E5%8A%A8%E6%A8%A1%E5%9E%8B/" class="post-title-link" itemprop="url">事件驱动模型</a>
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              <span class="post-meta-item-text">Posted on</span>

              <time title="Created: 2017-09-30 21:00:01" itemprop="dateCreated datePublished" datetime="2017-09-30T21:00:01+08:00">2017-09-30</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="fa fa-calendar-check-o"></i>
                </span>
                <span class="post-meta-item-text">Edited on</span>
                <time title="Modified: 2018-03-25 11:54:15" itemprop="dateModified" datetime="2018-03-25T11:54:15+08:00">2018-03-25</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <p>上次说了命令模式，然而作为好奇心比较足的服务端开发，就想到了事件驱动的模式不是和命令模式非常的像。同样的中间有个抽象层，一个是Command，另一个是Event。他们两者的区别是什么呢？先来温习一下事件驱动模型。</p>
          <!--noindex-->
            <div class="post-button">
              <a class="btn" href="/blog/2017/09/30/2018/%E4%BA%8B%E4%BB%B6%E9%A9%B1%E5%8A%A8%E6%A8%A1%E5%9E%8B/#more" rel="contents">
                Read more &raquo;
              </a>
            </div>
          <!--/noindex-->
        
      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

        
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block home" lang="zh-Hans">
    <link itemprop="mainEntityOfPage" href="https://guodh.gitee.io/blog/2017/09/29/2017/%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/blog/images/avatar.gif">
      <meta itemprop="name" content="RunningEgg">
      <meta itemprop="description" content="写下我的点点滴滴，写下我对这个世界的看法，还有我内心锁崇拜的技术">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="蛋蛋的小屋">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          
            <a href="/blog/2017/09/29/2017/%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F/" class="post-title-link" itemprop="url">命令模式</a>
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              <span class="post-meta-item-text">Posted on</span>

              <time title="Created: 2017-09-29 23:07:19" itemprop="dateCreated datePublished" datetime="2017-09-29T23:07:19+08:00">2017-09-29</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="fa fa-calendar-check-o"></i>
                </span>
                <span class="post-meta-item-text">Edited on</span>
                <time title="Modified: 2017-09-30 21:23:56" itemprop="dateModified" datetime="2017-09-30T21:23:56+08:00">2017-09-30</time>
              </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
          <p>在看MapReduce的源码时，看到了类似下面两段代码。因为我看到的版本是用Go写的，一时间觉得Go还厉害还能够传参传函数。但是仔细一想用Java也是可以做的，就像下面一些的写法。但是那么问题来了，为什么我平时写代码的时候却忽略传参是interface的情况？在什么样的情况下，传参使用interface会更有效？</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Reduce</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">doReduce</span><span class="params">()</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MapReduce</span> </span>&#123;    </span><br><span class="line">    <span class="function"><span class="keyword">int</span> <span class="title">doReduce</span><span class="params">(String fileName, String taskName, Reduce reduce)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> reduce.doReduce();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>先讲讲这样做的好处。这样就做到了面向接口编程，对扩展支持，对修改关闭。符合了5大原则之一的面向接口编程。</p>
<p>如何才能够使用好这种模式，让我重新去翻阅了Java所推崇的23中设计模式。感觉设计模式中的命令模式和我构想的传入函数这个想法比较贴切，那么就来看看命令模式是怎么做的。</p>
<h2 id="命令模式"><a href="#命令模式" class="headerlink" title="命令模式"></a>命令模式</h2><img src="https://olwr1lamu.qnssl.com/commandPatter.png" width="100%" height="100%" alt="命令模式UML"/>

<p>User通过Invoker去调用Recevier的action，然后命令模式做了一个很好的抽象，所有的动作是通过Command来执行。<br>代码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AudioPlayer</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">startCommand</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;开始演奏啦…………&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">stopCommand</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;停止演奏啦…………&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Commoand</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">excute</span><span class="params">()</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">KeyBoard</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">call</span><span class="params">(Commoand commoand)</span> </span>&#123;</span><br><span class="line">        commoand.excute();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">StartCommand</span> <span class="keyword">implements</span> <span class="title">Commoand</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> AudioPlayer audioPlayer;</span><br><span class="line"></span><br><span class="line">    StartCommand(AudioPlayer audioPlayer) &#123;</span><br><span class="line">        <span class="keyword">this</span>.audioPlayer = audioPlayer;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">excute</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        audioPlayer.startCommand();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">StopCommand</span> <span class="keyword">implements</span> <span class="title">Commoand</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> AudioPlayer audioPlayer;</span><br><span class="line"></span><br><span class="line">    StopCommand(AudioPlayer audioPlayer) &#123;</span><br><span class="line">        <span class="keyword">this</span>.audioPlayer = audioPlayer;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">excute</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        audioPlayer.stopCommand();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 最后的调用</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">     AudioPlayer audioPlayer = <span class="keyword">new</span> AudioPlayer();</span><br><span class="line"></span><br><span class="line">     KeyBoard keyBoard = <span class="keyword">new</span> KeyBoard();</span><br><span class="line"></span><br><span class="line">     StartCommand startCommand = <span class="keyword">new</span> StartCommand(audioPlayer);</span><br><span class="line">     StopCommand stopCommand = <span class="keyword">new</span> StopCommand(audioPlayer);</span><br><span class="line"></span><br><span class="line">     keyBoard.call(startCommand);</span><br><span class="line">     keyBoard.call(stopCommand);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>好奇的人肯定就会问了，我靠，这个有什么用？和直接调用Recevier的action有什么区别吗？我们在写个MoreCommand，你就知道了。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MoreCommand</span> <span class="keyword">implements</span> <span class="title">Commoand</span></span>&#123;</span><br><span class="line">    List&lt;Commoand&gt; commoandList = <span class="keyword">new</span> ArrayList&lt;Commoand&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">excute</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">for</span> (Commoand commoand : commoandList) &#123;</span><br><span class="line">            commoand.excute();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    MoreCommand moreCommand = <span class="keyword">new</span> MoreCommand();</span><br><span class="line">    keyBoard.call(moreCommand);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>抽象出一层的好处就可以体现出来啦，你可以通过队列去执行命令，同时在MoreCommand中可以对Command进行操作。</p>

      
    </div>

    
    
    
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

  </div>

  
  <nav class="pagination">
    <a class="extend prev" rel="prev" href="/blog/page/6/"><i class="fa fa-angle-left" aria-label="Previous page"></i></a><a class="page-number" href="/blog/">1</a><span class="space">&hellip;</span><a class="page-number" href="/blog/page/6/">6</a><span class="page-number current">7</span><a class="page-number" href="/blog/page/8/">8</a><a class="page-number" href="/blog/page/9/">9</a><a class="extend next" rel="next" href="/blog/page/8/"><i class="fa fa-angle-right" aria-label="Next page"></i></a>
  </nav>



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let activeClass = CONFIG.comments.activeClass;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          Table of Contents
        </li>
        <li class="sidebar-nav-overview">
          Overview
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">RunningEgg</p>
  <div class="site-description" itemprop="description">写下我的点点滴滴，写下我对这个世界的看法，还有我内心锁崇拜的技术</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/blog/archives/">
        
          <span class="site-state-item-count">89</span>
          <span class="site-state-item-name">posts</span>
        </a>
      </div>
      <div class="site-state-item site-state-tags">
        <span class="site-state-item-count">24</span>
        <span class="site-state-item-name">tags</span>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="mailto:guodh_1@163.com" title="E-Mail → mailto:guodh_1@163.com" rel="noopener" target="_blank"><i class="fa fa-fw fa-envelope"></i>E-Mail</a>
      </span>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">RunningEgg</span>
</div>
  <div class="powered-by">Powered by <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> v5.3.0
  </div>
  <span class="post-meta-divider">|</span>
  <div class="theme-info">Theme – <a href="https://pisces.theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Pisces</a> v7.7.1
  </div>

        








      </div>
    </footer>
  </div>

  
  <script src="/blog/lib/anime.min.js"></script>
  <script src="/blog/lib/velocity/velocity.min.js"></script>
  <script src="/blog/lib/velocity/velocity.ui.min.js"></script>

<script src="/blog/js/utils.js"></script>

<script src="/blog/js/motion.js"></script>


<script src="/blog/js/schemes/pisces.js"></script>


<script src="/blog/js/next-boot.js"></script>




  















  

  

</body>
</html>
